﻿#define _CRT_SECURE_NO_WARNINGS 1
//#include <stdio.h>
//二进制文件的打开 P125~126
/*
int main()
{
	FILE* pf = fopen("text.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int a = 10000;
	fwrite(&a, 4, 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}
*/  
//fopen函数      P100  P129
//功能：
// 是用来打开参数filename指定的文件，同时将打开的文件和⼀个流进行关联，后续对流的操作是通过fopen函数返回的指针来维护。
//具体对流（关联的文件）的操作是通过参数mode来指定的。
//FILE* fopen(const char* filename,const  char* mode);
//参数：
//filename：表示被打开的文件的名字，这个名字可以绝对路径，也可以是相对路径。
//mode： 表示对打开的文件的操作方式，具体见P129表格。
//返回值：
//若文件成功打开，该函数将返回一个指向FILE对象的指针，该指针可用于后续操作中标识对应的流。
//若打开失败，则返回 NULL 指针，所以一定要对fopen的返回值做判断，来验证文件是否打开成功。
/*
int main()
{
	FILE* pf = fopen("text.txt", "r");//以只读的形式打开text.txt文件
	if (pf == NULL)                        //若文件不存在，则打开失败
	{
		perror("fopen");
		return 1;
	}
	printf("打开成功");
	fclose(pf);
	pf = NULL;
	return 0;
}
*/
/*
int main()
{
	FILE* pf = fopen("data.txt", "w");
	//这里的路径是相对路径，表示在当前的工程目录下的data.txt文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//进行写操作
	//……
	fclose(pf);
	pf = NULL;
	return 0;
}*/
/*
int main()
{
	FILE* pf = fopen("./../ARM64/Debug/data.txt", "r");//相对路径
//表示在当前路径的上一级路径的ARM64文件中Debug文件中的data.txt文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	printf("打开成功");
	fclose(pf);
	pf = NULL;
	return 0;
}
*/
//fclose函数      P100   P129
//功能：关闭参数stream关联的文件，并取消其关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新：任何未写入的输出缓冲区内容将被写入，任何未读取的输入缓冲区内容将被丢弃。
//int fclose(FILE* stream);
//参数：stream指向要关闭的流的FILE对象的指针。
//返回值：成功关闭stream指向的流会返回0，否则会返回 EOF。

//fputc函数
//功能：
//将参数character指定的字符写入到stream指向的输出流中，通常用于向文件或标准输出流写入字符。
//在写入字符之后，还会调整指示器。字符会被写入流内部位置指示器当前指向的位置，随后该指示器自动向后移动一个位置。
//int fputc ( int character, FILE * stream );
//参数：
//character：被写入的字符；
//stream：是一个FILE* 类型的指针，指向了输出流（通常是文件流或stdout）。
//返回值：
//成功时返回写入的字符（以 int 形式）。
//失败时返回 EOF （通常是 -1），错误指示器会被设置，可通过ferror()检查具体错误。
//写入文件
/*
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写入字符
	int i;
	for (i = 'a'; i <='f'; i++)
	{
		fputc(i, pf);
	}
	fputc('g', pf);
	fclose(pf);
	pf = NULL;
	return 0;
}
*/
//写入标准输出流
/*

int main()
{
	int i;
	for (i = 'a'; i <= 'd'; i++)
	{
		fputc(i, stdout);
	}
	fputc('e', stdout);
	return 0;
}
//fputc()等价于putchar()
int main()
{
	int i;
	for (i = 'a'; i <= 'd'; i++)
	{
		putchar(i);
	}
	putchar('e');
	return 0;
}*/

//fgetc函数
//功能：从参数 stream 指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符，读取这个字符之后，文件指示器自动前进到下一个字符。
//int fgetc ( FILE * stream );
//参数：stream : FILE * 类型的文件指针，可以是 stdin ，也可以是其他输入流的指针。如果是 stdin 就从标准输入流读取数据。如果是文件流指针，就从文件读取数据。
//返回值：成功时返回读取的字符（以 int 形式）。
//若调用时流已处于文件末尾，函数返回 EOF 并设置流的文件结束指示器（ feof ）。
//若发生读取错误，函数返回 EOF 并设置流的错误指示器（ ferror ）。
//从文件中读取字符
/*
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取字符
	int i;
	for (i = 0; i < 4; i++)
	{
		int c = fgetc(pf);
		fputc(c, stdout);
		//也可以写成：
		//putchar(c);
		//printf("%c",c);
	}

	fclose(pf);
	pf = NULL;
	return 0;
}
*/
//从标准输入流读取字符
/*
int main()
{
	int c=fgetc(stdin);
	fputc(c, stdout);
	return 0;
}
//fgetc(stdin)等价于getchar()
int main()
{
	int c=getchar();
	putchar(c);
	return 0;
}
*/

//feof函数和ferror函数
//int feof(FILE* stream);         检测stream指针指向的流是否遇到文件末尾
//int ferror(FILE* stream);      检测stream指针指向的流是否发生读/写错误
//如果在读取文件的过程中，遇到了文件末尾，文件读取就会结束。这时读取函数会在对应的流上设置一个文件结束的指示符，这个文件结束指示符可以通过 feof 函数检测到。
//如果 feof 函数检测到文件结束指示符已经被设置，则返回非0的值，如果没有设置则返回0。
//如果在读/写文件的过程中，发生了读/写错误，文件读取就会结束。这时读/写函数会在对应的流上设置一个错误指示符，这个错误指示符可以通过 ferror 函数检测到。
//如果 ferror 函数检测到错误指示符已经被设置，则返回非0的值，如果没有设置则返回0。
//测试feof函数
/*
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int i;
	for (i = 0; i < 10; i++)
	{
		int c = fgetc(pf);
		if (c == EOF)
		{
			if (feof(pf))
				printf("遇到文件末尾\n");
			else if (ferror(pf))
				printf("发生读取错误\n");
		}
		else
		{
			fputc(c, stdout);
		}
	}
	fclose(pf);
	pf = NULL;
	return 0;
}*/
//测试ferror函数
/*
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int i;
	for (i = 0; i < 10; i++)
	{
		int c = fgetc(pf);
		if (c == EOF)
		{
			if (feof(pf))
				printf("遇到文件末尾\n");
			else if (ferror(pf))
				printf("发生读取错误\n");
		}
		else
		{
			fputc(c, stdout);
		}
	}
	fclose(pf);
	pf = NULL;
	return 0;
}
*/

//fputs函数
//功能：将参数 str 指向的字符串写入到参数 stream 指定的流中（不包含结尾的空字符 \0 ），适用于文件流或标准输出（stdout）。
//int fputs ( const char * str, FILE * stream );
//参数：
//str : str是指针，指向了要写入的字符串（必须以 \0 结尾）。
//stream ：是一个 FILE * 的指针，指向了要写入字符串的流。
//返回值：
//成功时返回非负整数。
//失败时返回 EOF （-1），同时会设置流的错误指示器，可以使用 ferror() 检查错误原因。
/*
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("abcdefg\0xxxxx", pf);
	fputs("hehe\n", pf);
	fputs("hi", pf);
	fclose(pf);
	pf = NULL;
	return 0;
}*/

//fgets函数
//功能：从stream指定输入流中读取字符串，至读取到换行符、文件末尾（EOF）或达到指定字符数(包含结尾的空字符 \0)，然后将读取到的字符串存储到str指向的空间中。
//char * fgets ( char * str, int num, FILE * stream );
//参数：
//str：是指向字符数组的指针，str指向的空间用于存储读取到的字符串。
//num：最大读取字符数（包含结尾的 \0 ，实际最多读取 num-1 个字符）。
//stream：输入流的文件指针（如文件流或 stdin ）。
//返回值：
//成功时返回 str 指针。
//若在尝试读取字符时遇到文件末尾，则设置文件结束指示器，并返回 NULL，需通过feof()检测。
//若发生读取错误，则设置流错误指示器，并返回NULL，通过ferror()检测。
/*
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	char arr[] = "xxxxxxxxxxxxxxx";
	fgets(arr, 7, pf);//实际是6个字符+'\0'  ：abcdef
	printf("%s\n", arr);
	char brr[] = "xxxxxxxxxxxxxxx";
	fgets(brr, 7, pf);//实际是6个字符+'\0'  ：ghehe\n
	printf("%s\n", brr);
	char crr[] = "xxxxxxxxxxxxxxx";
	fgets(crr, 4, pf);//实际是3个字符+'\0'  ：hi
	printf("%s\n", crr);
	fclose(pf);
	pf = NULL;
	return 0;
}*/

//fprintf函数
//功能：将格式化数据写入指定文件流的函数。它与 printf 类似，但可以输出到任意输出流（如磁盘文件、标准输出、标准错误等），而不仅限于控制台（标准输出）。
//int fprintf(FILE* stream, const char* format, ...);
//参数：
//stream：指向 FILE 对象的指针，表示要写入的文件流（如 stdout 、文件指针等）。
//format：格式化字符串，包含要写入的文本和格式说明符（如%d 、%s 等）。
//...        ：可变参数列表，提供与格式字符串中说明符对应的数据。
//返回值：
//成功时，返回写入的字符总数（非负值）。
//失败时，先设置对应流的错误指示器，再返回负值，可以通过 ferror() 来检测。
/*
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct S s = { "zhangsan",18,99.5f };
	printf("%s %d %f", s.name, s.age, s.score);
	fprintf(pf, "%s %d %f", s.name, s.age, s.score);
	fclose(pf);
	pf = NULL;
	return 0;
}
*/

//fscanf 函数
//功能：是从指定文件流中读取格式化数据的函数。它类似于 scanf ，但可以指定输入源(如文件、标准输入等)，而非仅限于控制台输入。适用于从文件解析结构化数据（如整数、浮点数、字符串等）。
//int fscanf(FILE* stream, const char* format, ...);
//参数：
//stream：指向 FILE 对象的指针，表示要读取的文件流（如stdin、文件指针等）。
//format：格式化字符串，定义如何解析输入数据（如%d、%f、%s等）。
//...        ：可变参数列表，提供存储数据的变量地址（需与格式字符串中的说明符匹配）。
//返回值：
//成功时，函数返回成功填充到参数列表中的项数。该值可能与预期项数一致，也可能因以下原因少于预期（甚至为零）：
//格式和数据匹配失败；
//读取发生错误；
//到达文件末尾（EOF）。
//如果在成功读取任何数据之前发生：
//发生读取错误，会在对应流上设置错误指示符，则返回 EOF 。
//到达文件末尾，会在对应流上设置文件结束指示符，则返回 EOF 。
/*
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct S s = { 0 };
	int r=fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);
	printf("%s %d %f\n", s.name, s.age, s.score);
	printf("%d\n", r);
	fclose(pf);
	pf = NULL;
	return 0;
}*/

//fwrite 函数
//功能：函数用于将数据块写入 stream 指向的文件流中，是以二进制的形式写入的。
//size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
//参数：
//ptr：指向要写入的数据块的指针。
//size：要写入的每个数据项的大小（以字节为单位）。
//count：要写入的数据项的数量。
//stream：指向 FILE 类型结构体的指针，指定了要写入数据的文件流。
//返回值：返回实际写入的数据项数量。如果发生错误，则返回值可能小于 count 。
/*
int main()
{
	FILE* pf = fopen("data.bin", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int arr[] = { 1,2,3,4,5 };
	if (fwrite(arr, sizeof(int), 5, pf) != 5)
	{
		perror("fwrite");
		return 1;
	}
	fclose(pf);
	pf = NULL;
	return 0;
}*/
/*
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	FILE* pf = fopen("data.bin", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct S s = {"zhangsan",18,66.6};
	fwrite(&s, sizeof(struct S), 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}*/
//使用注意事项：
//需要包含 <stdio.h> 头文件。
//在使用 fwrite() 之前，需要确保文件已经以二进制可写方式打开。
//fwrite() 通常用于二进制数据的写入，如果写入文本数据，请谨慎处理换行符和编码等问题。

//fread 函数
//功能：函数用于从 stream 指向的文件流中读取数据块，并将其存储到 ptr 指向的内存缓冲区中。
//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//参数：
//ptr：指向内存区域的指针，用于存储从文件中读取的数据。
//size：要读取的每个数据块的大小（以字节为单位）。
//count：要读取的数据块的数量。
//stream：指向 FILE 类型结构体的指针，指定了要从中读取数据的文件流。
//返回值：返回实际读取的数据块数量。
/*
int main()
{
	FILE* pf = fopen("data.bin", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int arr[5] = {0};
	size_t r = fread(arr, sizeof(int), 5, pf);
	if (r != 5)
	{ 
		if (feof(pf))
			printf("遇到文件末尾\n");
		else if (ferror(pf))
			printf("读取出错\n");
	}
	else
	{
		int i;
		for (i = 0; i < 5; i++)
		{ 
			printf("%d ", arr[i]);
		}
	}
	fclose(pf);
	pf = NULL;
	return 0;
}*/
/*
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	FILE* pf = fopen("data.bin", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct S s = {0};
	fread(&s, sizeof(struct S), 1, pf);
	printf("%s %d %f", s.name, s.age, s.score);
	fclose(pf);
	pf = NULL;
	return 0;
}*/
//使用注意事项：
//需要包含 <stdio.h> 头文件。
//在使用 fread() 之前，需要确保文件已经以二进制可读方式打开。
//ptr 指向的内存区域必须足够大，以便存储指定数量和大小的数据块。
//如果fread()成功读取了指定数量的数据块，则返回值等于count；如果读取数量少于count ，则可能已经到达文件结尾或者发生了错误。
//在二进制文件读取时，fread()是常用的函数，但对于文本文件读取，通常使用 fgets()或 fscanf() 。

//sprintf 函数
//功能：将格式化数据写入字符数组(字符串)。它类似于printf，但输出目标不是控制台或文件，而是用户指定的内存缓冲区。常用于动态生成字符串、拼接数据或转换数据格式。简而言之就是将格式化的数据转换成一个字符串。
//int sprintf(char* str, const char* format, ...);
//参数：
//str       ：指向字符数组的指针，用于存储生成的字符串（需确保足够大以防止溢出）。
//format：格式化字符串，定义输出格式（如%d 、%f 、%s等）。
//...         ：可变参数列表，提供与格式字符串中说明符对应的数据。
//返回值：
//成功时：返回写入buffer的字符数（不包括结尾的空字符 \0 ）。
//失败时：返回负值。
/*
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "zhangsan", 18, 88.8f };
	char arr[50] = { 0 };
	sprintf(arr, "%s %d %f", s.name, s.age, s.score );
	printf("%s", arr);
	return 0;
}*/

//sscanf 函数
//功能：从字符串中读取格式化数据。它与 scanf 类似，但输入源是内存中的字符串而非控制台或文件。常用于解析字符串中的结构化数据（如提取数字、分割文本等）。
//int sscanf(const char* str, const char* format, ...);
//参数：
//str       ：要解析的源字符串（输入数据来源）。
//format：格式化字符串，定义如何解析数据（如%d、%f、%s等）。
//...         ：可变参数列表，提供存储数据的变量地址（需与格式字符串中的说明符匹配）。
//返回值：
//成功时：返回成功解析并赋值的参数数量（非负值）。
//失败或未匹配任何数据：若输入结束或解析失败，返回EOF。
/*
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = {0};
	char arr[50] = {"zhangsan 18 88.8"};
	sscanf(arr, "%s %d %f",s.name, &s.age, &s.score);
	printf("%s %d %f", s.name, s.age, s.score);
	return 0;
}*/

//fseek 函数
//根据文件指针的位置和偏移量来定位文件指示器(文件内容的光标)。
//int fseek(FILE* stream, long int offset, int origin);
/*
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("abcdefghi", pf);
	fseek(pf, 5, SEEK_SET);
	fputc('k', pf);
	fseek(pf, 2, SEEK_CUR);
	fputs("haha", pf);
	fseek(pf, -9, SEEK_END);
	fputc('8', pf);
	fclose(pf);
	pf = NULL;
	return 0;
}*/
/*
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, 5, SEEK_SET);
	int c = fgetc(pf);
	printf("%c\n", c);//k
	fseek(pf, 3, SEEK_CUR);
	c = fgetc(pf);
	printf("%c\n", c);//a
	fseek(pf, -9, SEEK_END);
	c = fgetc(pf);
	printf("%c\n", c);//8
	fclose(pf);
	pf = NULL;
	return 0;
}*/

//ftell 函数
//返回文件指针（文件指示器）相对于起始位置的偏移量
//long int ftell(FILE* stream);
/*
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, -9, SEEK_END);
	int c = fgetc(pf);
	printf("%c\n", c);//8
	int r=ftell(pf);
	printf("%d", r);//4
	fclose(pf);
	pf = NULL;
	return 0;
}*/

//rewind 函数
//让文件指针的位置回到文件的起始位置
//void rewind ( FILE * stream );
/*
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, 6, SEEK_SET);
	int c = fgetc(pf);
	printf("%c\n", c);
	rewind(pf);
	c = fgetc(pf);
	printf("%c\n", c);
	//rewind()等价于fseek(pf,0,SEEK_SET)
	fseek(pf, 0, SEEK_SET);
	c = fgetc(pf);
	printf("%c\n", c);
	fclose(pf);
	pf = NULL;
	return 0;
}*/

//fflush 函数
//功能：强制刷新参数 stream 指定流的缓冲区，确保数据写入底层设备。
//对输出流：将缓冲区中未写入的数据立即写入文件。
//对输入流：行为由具体实现决定，非C语言标准行为（可能清空输入缓冲区）。
//参数为 NULL 时：刷新所有打开的输出流。
//int fflush(FILE* stream);
//参数：stream指向文件流的指针（如stdout、文件指针等）
//返回值：成功返回0，失败返回EOF。
/*
#include <stdio.h>
#include <windows.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了，打开data.txt文件，发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时，才将输出缓冲区的数据写到文件（磁盘）
	//注：fflush 在高版本的VS上不能使用了
	printf("再睡眠10秒-此时，再次打开data.txt文件，文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注：fclose在关闭文件的时候，也会刷新缓冲区
	pf = NULL;
	return 0;
}*/
//注意事项：
//1. 仅对输出流或更新流（最后一次操作为输出）有明确刷新行为
//2. 输入流的刷新行为不可移植（如清空输入缓冲区是非标准特性）
//3. 程序正常终止（ exit ）或调用 fclose 时会自动刷新，但程序崩溃时缓冲区数据可能丢失
//结论：因为有缓冲区的存在，C语言在操作文件的时候，需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做，可能导致读写文件的问题。
#include <stdio.h>
int main()
{
	FILE* fp = fopen("data.txt", "w+");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写abcdefghi到文件中
	fputs("abcdefghi", fp);
	//刷新缓冲区，保证数据写入文件
	fflush(fp);
	//要读取数据b字符，先定位文件指针
	fseek(fp, 1, SEEK_SET);
	int ch = fgetc(fp);//读取字符
	printf("%c\n", ch);
	//在b的位置开始写入hello
	fseek(fp, -1, SEEK_CUR);
	fputs("hello", fp);
	fclose(fp);
	fp = NULL;
	return 0;
}


